Popular Searches
Popular Course Categories
Popular Courses

Understanding state and StatefulWidget

Understanding state and StatefulWidget

Flutter Fundamentals

Understanding State and StatefulWidget in Flutter

State is one of the most important concepts in Flutter because it allows an application's user interface to respond to changing data and user interactions. A StatefulWidget is used when a widget needs to maintain information that can change during its lifetime.

For example, a counter value can change from 0 to 1, a checkbox can change from unchecked to checked, a switch can change from off to on, and a loading indicator can change after an asynchronous operation completes.

Flutter's official documentation defines state as information that can be read when a widget is built and may change during the widget's lifetime. A StatefulWidget is used when the described UI needs to change dynamically. Read the Flutter StatefulWidget API documentation.

For structured Flutter learning, visit JustAcademy Flutter Training and Register for a Flutter Course Demo.


1. What is State?

State is data or information that can change while a widget is being used.

Examples of state include:

  • Counter value
  • Selected checkbox value
  • Selected radio button
  • Switch on/off status
  • Current text entered in a field
  • Selected tab
  • Current page number
  • Loading status
  • Favorite status
  • Selected product quantity
  • Animation progress
  • Form validation status

Simple Example

int count = 0;

Here, count represents state. If the value changes from 0 to 1, the UI may need to display the new value.


2. What is StatefulWidget?

A StatefulWidget is a widget that works with mutable state. Unlike a StatelessWidget, a StatefulWidget can maintain changing information through a separate State object.

It is important to understand that the StatefulWidget itself is immutable. The mutable information is normally stored in its associated State object.

StatefulWidget
      |
      | createState()
      v
State Object
      |
      | build()
      v
User Interface
      |
      | setState()
      v
UI Rebuild

Official reference: Flutter StatefulWidget API.


3. Why Do We Need State?

Modern applications are interactive. The UI must often respond to user actions and changing information.

Consider a shopping application. When a user clicks the + button next to a product, the quantity changes.

Before:
Quantity: 1

User clicks +

After:
Quantity: 2

The quantity is state because it changes during the lifetime of the UI.

Similarly, consider a login form:

Before:
Loading = false

User clicks Login

During request:
Loading = true

After request:
Loading = false

The loading status is also state.


4. StatelessWidget vs StatefulWidget

FeatureStatelessWidgetStatefulWidget
Mutable internal stateNot requiredSupported through State
Separate State classNoYes
setState()Not available directlyAvailable in State
Dynamic UICan rebuild when configuration changesCan rebuild when internal state changes
Typical usageStatic/configuration-driven UIInteractive/dynamic UI
ExampleLogo, heading, static cardCounter, form, switch

5. Basic Structure of StatefulWidget

A StatefulWidget normally consists of two classes.

  1. A class that extends StatefulWidget.
  2. A State class that extends State.
class MyWidget extends StatefulWidget {
  const MyWidget({super.key});

  @override
  State createState() => _MyWidgetState();
}

class _MyWidgetState extends State {
  @override
  Widget build(BuildContext context) {
    return const Text('Hello Flutter');
  }
}

Explanation

  • MyWidget is the StatefulWidget.
  • _MyWidgetState contains the mutable state and UI logic.
  • createState() creates the State object.
  • build() describes the current UI.

6. Understanding the Relationship Between Widget and State

The StatefulWidget contains the widget's configuration, while the State object contains information that can change.

StatefulWidget
├── Immutable configuration
│   ├── title
│   ├── color
│   └── other properties
│
└── State
    ├── counter
    ├── selected value
    ├── loading status
    └── other mutable data

For example:

class UserProfile extends StatefulWidget {
  final String name;

  const UserProfile({
    super.key,
    required this.name,
  });

  @override
  State createState() => _UserProfileState();
}

class _UserProfileState extends State {
  bool isFavorite = false;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text(widget.name),
        IconButton(
          onPressed: () {
            setState(() {
              isFavorite = !isFavorite;
            });
          },
          icon: Icon(
            isFavorite
                ? Icons.favorite
                : Icons.favorite_border,
          ),
        ),
      ],
    );
  }
}

Here, name is part of the widget configuration, while isFavorite is mutable state.


7. The State Object

The State class contains the logic and internal state associated with a StatefulWidget.

Official reference: Flutter State API.

class _CounterState extends State {
  int count = 0;

  @override
  Widget build(BuildContext context) {
    return Text('$count');
  }
}

In this example, count is state because it can change during the lifetime of the widget.


8. createState()

The createState() method creates the State object associated with a StatefulWidget.

class Counter extends StatefulWidget {
  const Counter({super.key});

  @override
  State createState() => _CounterState();
}

Flutter calls createState() when it needs to create the State associated with that widget at a location in the widget tree.


9. Understanding setState()

setState() is one of the most important methods when working with local state in StatefulWidget.

It tells Flutter that the State object has changed in a way that may affect the UI, so Flutter should schedule the widget for rebuilding.

setState(() {
  count++;
});

Official reference: Flutter setState() API.


10. Without setState()

Consider:

int count = 0;

void increment() {
  count++;
}

The variable changes, but Flutter has not been notified that the visible UI should be rebuilt.

The usual approach for local StatefulWidget state is:

void increment() {
  setState(() {
    count++;
  });
}

This makes the state change visible to Flutter's rebuild process.


11. Counter Example

import 'package:flutter/material.dart';

class CounterPage extends StatefulWidget {
  const CounterPage({super.key});

  @override
  State createState() => _CounterPageState();
}

class _CounterPageState extends State {
  int count = 0;

  void increment() {
    setState(() {
      count++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Counter'),
      ),
      body: Center(
        child: Text(
          '$count',
          style: const TextStyle(
            fontSize: 40,
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: increment,
        child: const Icon(Icons.add),
      ),
    );
  }
}

Flow

  1. Initial state is count = 0.
  2. The user presses the button.
  3. increment() runs.
  4. setState() updates count.
  5. Flutter schedules the State object for rebuilding.
  6. build() runs again.
  7. The updated count appears on the screen.

12. Types of State

State can be broadly understood as information that affects the UI and changes over time.

Local UI State

State that belongs to a particular widget.

bool isSelected = false;

Form State

State related to form fields, validation, and submission.

String email = '';

Loading State

State that indicates whether an operation is currently running.

bool isLoading = false;

Selection State

State that identifies a selected item.

int selectedIndex = 0;

Application State

State that may be needed by multiple parts of an application, such as authentication information, cart data, or user preferences. This kind of state may be managed above individual widgets or with a state-management solution.


13. State Lifecycle

A State object follows a lifecycle controlled by Flutter.

createState()
     ↓
State created
     ↓
mounted
     ↓
initState()
     ↓
didChangeDependencies()
     ↓
build()
     ↓
setState()
     ↓
build() again
     ↓
didUpdateWidget() when configuration changes
     ↓
deactivate()
     ↓
dispose()
     ↓
unmounted

The exact callbacks that run depend on how the widget is inserted, updated, moved, and removed from the widget tree.


14. initState()

initState() is called when the State object is initialized. It is normally used for one-time initialization.

@override
void initState() {
  super.initState();

  print('State initialized');
}

Common uses include:

  • Initializing controllers
  • Initializing variables
  • Starting timers
  • Setting up animation controllers
  • Starting appropriate subscriptions
  • Performing initialization that depends on the widget or context when permitted

15. didChangeDependencies()

didChangeDependencies() is called after initState() and can be called again when inherited dependencies change.

@override
void didChangeDependencies() {
  super.didChangeDependencies();

  print('Dependencies changed');
}

This method can be useful when initialization or work depends on inherited widgets.


16. build()

The build() method describes the user interface based on the current widget configuration, state, and available context.

@override
Widget build(BuildContext context) {
  return Scaffold(
    body: Center(
      child: Text('$count'),
    ),
  );
}

The build method may execute many times, so it should remain focused on describing the UI and should avoid unnecessary expensive work.


17. didUpdateWidget()

When a parent rebuilds and provides a new widget configuration with the same runtime type and key at the same location, Flutter can update the existing State object's widget property and call didUpdateWidget().

@override
void didUpdateWidget(covariant UserWidget oldWidget) {
  super.didUpdateWidget(oldWidget);

  if (oldWidget.name != widget.name) {
    print('User name changed');
  }
}

This method is useful when the State object needs to respond to changes in the StatefulWidget's properties.


18. dispose()

dispose() is called when the State object is permanently removed from the widget tree.

It should be used to release resources owned by the State object.

@override
void dispose() {
  controller.dispose();
  timer?.cancel();
  super.dispose();
}

Common resources that may need cleanup include:

  • TextEditingController
  • AnimationController
  • Timer
  • Stream subscriptions
  • Listeners
  • Other resources owned by the State object

19. Understanding mounted

The mounted property tells you whether the State object is currently associated with a BuildContext in the widget tree.

if (!mounted) {
  return;
}

setState(() {
  isLoading = false;
});

This can be useful after asynchronous work when the widget may have been removed before the operation completes.

It is generally better to cancel work that should no longer continue where possible, rather than depending only on a mounted check.


20. State and User Interaction

State becomes especially useful when the user interacts with the application.

Example: Switch

class SettingsSwitch extends StatefulWidget {
  const SettingsSwitch({super.key});

  @override
  State createState() => _SettingsSwitchState();
}

class _SettingsSwitchState extends State {
  bool enabled = false;

  @override
  Widget build(BuildContext context) {
    return Switch(
      value: enabled,
      onChanged: (value) {
        setState(() {
          enabled = value;
        });
      },
    );
  }
}

Here, enabled is state because its value changes when the user interacts with the switch.


21. State with Checkbox

class TermsWidget extends StatefulWidget {
  const TermsWidget({super.key});

  @override
  State createState() => _TermsWidgetState();
}

class _TermsWidgetState extends State {
  bool accepted = false;

  @override
  Widget build(BuildContext context) {
    return CheckboxListTile(
      title: const Text('Accept Terms'),
      value: accepted,
      onChanged: (value) {
        setState(() {
          accepted = value ?? false;
        });
      },
    );
  }
}

22. State with TextField

class NameForm extends StatefulWidget {
  const NameForm({super.key});

  @override
  State createState() => _NameFormState();
}

class _NameFormState extends State {
  String name = '';

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        TextField(
          onChanged: (value) {
            setState(() {
              name = value;
            });
          },
          decoration: const InputDecoration(
            labelText: 'Enter your name',
          ),
        ),
        const SizedBox(height: 16),
        Text(
          name.isEmpty
              ? 'Enter your name'
              : 'Hello $name',
        ),
      ],
    );
  }
}

Every time the input changes, the state is updated and the relevant UI can rebuild.


23. State with Slider

class VolumeControl extends StatefulWidget {
  const VolumeControl({super.key});

  @override
  State createState() => _VolumeControlState();
}

class _VolumeControlState extends State {
  double volume = 50;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text('Volume: ${volume.toInt()}'),
        Slider(
          value: volume,
          min: 0,
          max: 100,
          onChanged: (value) {
            setState(() {
              volume = value;
            });
          },
        ),
      ],
    );
  }
}

24. State with Conditional UI

State can determine which widgets should be displayed.

class LoginStatus extends StatefulWidget {
  const LoginStatus({super.key});

  @override
  State createState() => _LoginStatusState();
}

class _LoginStatusState extends State {
  bool loggedIn = false;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text(
          loggedIn
              ? 'Welcome Back!'
              : 'Please Login',
        ),
        ElevatedButton(
          onPressed: () {
            setState(() {
              loggedIn = !loggedIn;
            });
          },
          child: Text(
            loggedIn
                ? 'Logout'
                : 'Login',
          ),
        ),
      ],
    );
  }
}

25. State and Asynchronous Operations

Applications frequently perform asynchronous tasks such as network requests, database operations, or file operations. A common pattern is to maintain a loading state.

bool isLoading = false;

Future loadData() async {
  setState(() {
    isLoading = true;
  });

  await Future.delayed(
    const Duration(seconds: 2),
  );

  if (!mounted) {
    return;
  }

  setState(() {
    isLoading = false;
  });
}

Important Rule

Do not make the callback passed to setState() asynchronous.

Incorrect:

setState(() async {
  await loadData();
});

Correct:

await loadData();

if (!mounted) {
  return;
}

setState(() {
  isLoading = false;
});

26. Multiple State Variables

A StatefulWidget can have multiple state variables.

class UserSettings extends StatefulWidget {
  const UserSettings({super.key});

  @override
  State createState() => _UserSettingsState();
}

class _UserSettingsState extends State {
  bool notifications = true;
  bool darkMode = false;
  double fontSize = 16;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        SwitchListTile(
          title: const Text('Notifications'),
          value: notifications,
          onChanged: (value) {
            setState(() {
              notifications = value;
            });
          },
        ),
        SwitchListTile(
          title: const Text('Dark Mode'),
          value: darkMode,
          onChanged: (value) {
            setState(() {
              darkMode = value;
            });
          },
        ),
        Slider(
          value: fontSize,
          min: 12,
          max: 30,
          onChanged: (value) {
            setState(() {
              fontSize = value;
            });
          },
        ),
      ],
    );
  }
}

27. State and Constructor Parameters

A StatefulWidget can receive immutable configuration through constructor parameters.

class ProfileWidget extends StatefulWidget {
  final String name;

  const ProfileWidget({
    super.key,
    required this.name,
  });

  @override
  State createState() => _ProfileWidgetState();
}

class _ProfileWidgetState
    extends State {
  bool isFavorite = false;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text(widget.name),
        IconButton(
          onPressed: () {
            setState(() {
              isFavorite = !isFavorite;
            });
          },
          icon: Icon(
            isFavorite
                ? Icons.favorite
                : Icons.favorite_border,
          ),
        ),
      ],
    );
  }
}

Here:

  • widget.name comes from the StatefulWidget configuration.
  • isFavorite belongs to the State object.

28. Understanding widget in State

Inside a State class, the widget property refers to the current StatefulWidget configuration.

class WelcomeWidget extends StatefulWidget {
  final String username;

  const WelcomeWidget({
    super.key,
    required this.username,
  });

  @override
  State createState() => _WelcomeWidgetState();
}

class _WelcomeWidgetState
    extends State {
  @override
  Widget build(BuildContext context) {
    return Text(
      'Welcome ${widget.username}',
    );
  }
}

29. Local State vs Shared State

Local State

Local state belongs to a particular widget or small part of the UI.

Examples:

  • Whether a password is visible
  • Whether a card is expanded
  • Current value of a local slider
  • Selected local tab

Shared State

Shared state is information that multiple parts of an application need to access or modify.

Examples:

  • Logged-in user information
  • Shopping cart
  • Application settings
  • Authentication status
  • Shared preferences

For larger applications, shared state can be managed using appropriate state-management patterns or packages rather than keeping all state in one large StatefulWidget.


30. Lifting State Up

When multiple child widgets need access to the same state, the state can be moved to a common parent.

Parent StatefulWidget
        |
        +---- Child A
        |
        +---- Child B

Example:

class ParentWidget extends StatefulWidget {
  const ParentWidget({super.key});

  @override
  State createState() => _ParentWidgetState();
}

class _ParentWidgetState extends State {
  int count = 0;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text('Count: $count'),
        ChildButton(
          onPressed: () {
            setState(() {
              count++;
            });
          },
        ),
      ],
    );
  }
}

class ChildButton extends StatelessWidget {
  final VoidCallback onPressed;

  const ChildButton({
    super.key,
    required this.onPressed,
  });

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: onPressed,
      child: const Text('Increase'),
    );
  }
}

Here, the parent owns the state and the child receives a callback.


31. Why Should State Be Kept Close to Where It Is Used?

Flutter's StatefulWidget documentation recommends pushing frequently changing state toward the leaves of the widget tree when practical. This can reduce the amount of UI that needs to rebuild when the state changes.

Large Page
├── Header
├── Product List
├── Clock Widget
└── Footer

If only the clock changes every second, it can be useful to keep its changing state inside a dedicated Clock widget instead of making the entire page rebuild for every tick.

This approach can improve organization and reduce unnecessary rebuild work.


32. StatefulWidget and Performance

A StatefulWidget that frequently calls setState() may rebuild many times during its lifetime. Therefore, it is important to keep the rebuild scope appropriate.

Useful Practices

  • Keep frequently changing state close to the widgets that use it.
  • Split large UI components into smaller widgets.
  • Use const widgets where possible.
  • Avoid unnecessary expensive calculations in build().
  • Reuse widgets when appropriate.
  • Avoid rebuilding large parts of the widget tree for small state changes.

Flutter's official StatefulWidget documentation provides additional performance guidance. See StatefulWidget performance considerations.


33. Using const with StatefulWidget

The StatefulWidget class itself can have a const constructor even though its associated State object contains mutable state.

class Counter extends StatefulWidget {
  const Counter({super.key});

  @override
  State createState() => _CounterState();
}

The const constructor applies to the immutable widget configuration. The mutable counter value remains inside the State object.


34. StatefulWidget with Timer

A timer is a practical example of state that changes automatically over time.

import 'dart:async';
import 'package:flutter/material.dart';

class TimerWidget extends StatefulWidget {
  const TimerWidget({super.key});

  @override
  State createState() => _TimerWidgetState();
}

class _TimerWidgetState
    extends State {
  int seconds = 0;
  Timer? timer;

  @override
  void initState() {
    super.initState();

    timer = Timer.periodic(
      const Duration(seconds: 1),
      (_) {
        if (!mounted) {
          return;
        }

        setState(() {
          seconds++;
        });
      },
    );
  }

  @override
  void dispose() {
    timer?.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Text(
      'Seconds: $seconds',
      style: const TextStyle(
        fontSize: 24,
      ),
    );
  }
}

This example demonstrates initialization, changing state, rebuilding the UI, checking mounted, and cleanup.


35. StatefulWidget with TextEditingController

class EmailField extends StatefulWidget {
  const EmailField({super.key});

  @override
  State createState() => _EmailFieldState();
}

class _EmailFieldState
    extends State {
  final TextEditingController controller =
      TextEditingController();

  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return TextField(
      controller: controller,
      decoration: const InputDecoration(
        labelText: 'Email',
        border: OutlineInputBorder(),
      ),
    );
  }
}

The controller is initialized as a State field and disposed when the State object is removed.


36. StatefulWidget with Form Validation

class LoginForm extends StatefulWidget {
  const LoginForm({super.key});

  @override
  State createState() => _LoginFormState();
}

class _LoginFormState
    extends State {
  final GlobalKey formKey =
      GlobalKey();

  @override
  Widget build(BuildContext context) {
    return Form(
      key: formKey,
      child: Column(
        children: [
          TextFormField(
            decoration: const InputDecoration(
              labelText: 'Email',
            ),
            validator: (value) {
              if (value == null || value.isEmpty) {
                return 'Please enter your email';
              }

              return null;
            },
          ),
          ElevatedButton(
            onPressed: () {
              if (formKey.currentState!.validate()) {
                print('Form is valid');
              }
            },
            child: const Text('Submit'),
          ),
        ],
      ),
    );
  }
}

37. Common Mistakes When Working with State

Mistake 1: Changing State Without setState()

count++;

For local State-driven UI, use:

setState(() {
  count++;
});

Mistake 2: Making setState() Asynchronous

setState(() async {
  await someTask();
});

Do not return a Future from the callback passed to setState().

Mistake 3: Forgetting dispose()

Controllers, timers, and subscriptions may need cleanup.

Mistake 4: Calling setState() After dispose()

Once a State object has been disposed, calling setState() on it is an error.

Mistake 5: Keeping Too Much State in One Widget

A large StatefulWidget can become difficult to maintain. Split independent UI sections into smaller widgets when appropriate.

Mistake 6: Expensive Operations in build()

Because build() can run repeatedly, avoid unnecessary expensive work inside it.


38. Best Practices for Managing State

  • Keep state as close as practical to the widgets that use it.
  • Use StatefulWidget for appropriate local mutable state.
  • Use setState() to notify Flutter about UI-relevant local state changes.
  • Keep the setState callback synchronous.
  • Use final for immutable widget properties.
  • Use const where appropriate.
  • Initialize resources in initState().
  • Clean up resources in dispose().
  • Check mounted after asynchronous operations when necessary.
  • Break large widgets into smaller reusable widgets.
  • Lift state to a common parent when multiple children need the same state.
  • Use a dedicated state-management approach when application-wide or complex shared state requires it.

39. Practical Example: Shopping Cart Quantity

A shopping cart is a useful example of state.

class ProductQuantity extends StatefulWidget {
  const ProductQuantity({super.key});

  @override
  State createState() => _ProductQuantityState();
}

class _ProductQuantityState
    extends State {
  int quantity = 1;

  void increaseQuantity() {
    setState(() {
      quantity++;
    });
  }

  void decreaseQuantity() {
    if (quantity > 1) {
      setState(() {
        quantity--;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        IconButton(
          onPressed: decreaseQuantity,
          icon: const Icon(Icons.remove),
        ),
        Text(
          '$quantity',
          style: const TextStyle(
            fontSize: 20,
          ),
        ),
        IconButton(
          onPressed: increaseQuantity,
          icon: const Icon(Icons.add),
        ),
      ],
    );
  }
}

Here, quantity is state because it changes based on user interaction.


40. Practical Example: Favorite Product

class ProductFavorite extends StatefulWidget {
  const ProductFavorite({super.key});

  @override
  State createState() => _ProductFavoriteState();
}

class _ProductFavoriteState
    extends State {
  bool favorite = false;

  @override
  Widget build(BuildContext context) {
    return IconButton(
      onPressed: () {
        setState(() {
          favorite = !favorite;
        });
      },
      icon: Icon(
        favorite
            ? Icons.favorite
            : Icons.favorite_border,
      ),
    );
  }
}

41. Practical Example: Show and Hide Password

class PasswordInput extends StatefulWidget {
  const PasswordInput({super.key});

  @override
  State createState() => _PasswordInputState();
}

class _PasswordInputState
    extends State {
  bool obscureText = true;

  @override
  Widget build(BuildContext context) {
    return TextField(
      obscureText: obscureText,
      decoration: InputDecoration(
        labelText: 'Password',
        suffixIcon: IconButton(
          icon: Icon(
            obscureText
                ? Icons.visibility
                : Icons.visibility_off,
          ),
          onPressed: () {
            setState(() {
              obscureText = !obscureText;
            });
          },
        ),
      ),
    );
  }
}

42. Complete Example: Interactive Student Dashboard

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: const StudentDashboard(),
    );
  }
}

class StudentDashboard extends StatefulWidget {
  const StudentDashboard({super.key});

  @override
  State createState() =>
      _StudentDashboardState();
}

class _StudentDashboardState
    extends State {
  String studentName = '';
  int completedLessons = 0;
  bool courseCompleted = false;

  void completeLesson() {
    setState(() {
      completedLessons++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Student Dashboard'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(20),
        child: Column(
          crossAxisAlignment:
              CrossAxisAlignment.start,
          children: [
            TextField(
              onChanged: (value) {
                setState(() {
                  studentName = value;
                });
              },
              decoration: const InputDecoration(
                labelText: 'Student Name',
                border: OutlineInputBorder(),
              ),
            ),
            const SizedBox(height: 20),
            Text(
              studentName.isEmpty
                  ? 'Welcome Student'
                  : 'Welcome $studentName',
              style: const TextStyle(
                fontSize: 22,
                fontWeight: FontWeight.bold,
              ),
            ),
            const SizedBox(height: 20),
            Text(
              'Completed Lessons: $completedLessons',
              style: const TextStyle(fontSize: 18),
            ),
            const SizedBox(height: 10),
            ElevatedButton(
              onPressed: completeLesson,
              child: const Text('Complete Lesson'),
            ),
            const SizedBox(height: 20),
            SwitchListTile(
              title: const Text('Course Completed'),
              value: courseCompleted,
              onChanged: (value) {
                setState(() {
                  courseCompleted = value;
                });
              },
            ),
            Text(
              courseCompleted
                  ? 'Congratulations! Course completed.'
                  : 'Course is still in progress.',
            ),
          ],
        ),
      ),
    );
  }
}

State Used in This Example

  • studentName stores the entered name.
  • completedLessons stores the number of completed lessons.
  • courseCompleted stores the completion status.

43. Widget Tree of the Example

MaterialApp
└── Scaffold
    ├── AppBar
    │   └── Text
    └── Body
        └── Column
            ├── TextField
            ├── Text
            ├── Text
            ├── ElevatedButton
            ├── SwitchListTile
            └── Text

The StatefulWidget manages the changing values while the widget tree displays those values.


44. State Change Flow

A typical local state update follows this process:

User Interaction
      ↓
Event Handler
      ↓
Change State
      ↓
setState()
      ↓
Flutter schedules rebuild
      ↓
build()
      ↓
Updated Widget Tree
      ↓
Updated UI

Example

User taps +
      ↓
increment()
      ↓
count++
      ↓
setState()
      ↓
build()
      ↓
Text('$count')
      ↓
New count appears

45. State and Widget Tree

Flutter's UI is represented as a widget tree. StatefulWidget allows a particular part of that tree to maintain changing information.

Application
│
├── Header
│
├── ProductSection
│   ├── ProductCard
│   ├── ProductCard
│   └── ProductCard
│
└── CartWidget
    └── QuantityState

If the quantity changes, the UI associated with that state can be rebuilt without requiring every unrelated part of the application to maintain that same state.


46. When Should You Use StatefulWidget?

StatefulWidget is appropriate when a widget owns mutable UI state that changes during its lifetime.

  • Counter applications
  • Interactive forms
  • Checkboxes
  • Switches
  • Sliders
  • Expandable sections
  • Favorite buttons
  • Loading indicators
  • Timers
  • Animations
  • Temporary selection state
  • Interactive controls

47. When Should You Use StatelessWidget?

If a component does not need to own mutable internal state and can render from its configuration and context, StatelessWidget may be a better choice.

class CourseTitle extends StatelessWidget {
  final String title;

  const CourseTitle({
    super.key,
    required this.title,
  });

  @override
  Widget build(BuildContext context) {
    return Text(
      title,
      style: const TextStyle(
        fontSize: 24,
        fontWeight: FontWeight.bold,
      ),
    );
  }
}

48. Interview Questions

Q1. What is state in Flutter?

State is information that can be read when the widget is built and can change during the widget's lifetime.

Q2. What is StatefulWidget?

StatefulWidget is a widget used when the UI needs to work with mutable state.

Q3. Is StatefulWidget itself mutable?

No. StatefulWidget instances are immutable. Mutable state is normally held by the associated State object.

Q4. Where is mutable state stored?

Mutable state is normally stored inside the State object associated with the StatefulWidget.

Q5. What is createState()?

It creates the State object for a StatefulWidget at a particular location in the widget tree.

Q6. What does setState() do?

It tells Flutter that the State object has changed in a way that may affect the UI and schedules a rebuild.

Q7. What is initState()?

It is a lifecycle method used for one-time initialization of a State object.

Q8. What is dispose()?

It is called when the State object is permanently removed and is used to release resources.

Q9. What is mounted?

It indicates whether the State object is currently associated with a BuildContext in the widget tree.

Q10. Why should state be kept close to the widgets that use it?

Keeping frequently changing state close to the relevant UI can reduce unnecessary rebuilding and make the application easier to organize.

Q11. Can a StatefulWidget receive constructor parameters?

Yes. Constructor parameters represent immutable configuration and can be accessed from the State object through widget.

Q12. Can a StatelessWidget use state from its parent?

Yes. A parent can own state and pass the current value and callbacks to a StatelessWidget.


49. Practice Exercise

Create a Flutter Student Profile application using StatefulWidget.

Requirements

  1. Create a StatefulWidget named StudentProfile.
  2. Add a student name.
  3. Add a course name.
  4. Add a counter for completed modules.
  5. Add a favorite button.
  6. Add a switch for course completion.
  7. Add a TextField for changing the student's name.
  8. Use setState() whenever UI state changes.
  9. Use initState() if initialization is required.
  10. Use dispose() if controllers or other disposable resources are created.

Suggested State

String studentName = '';
int completedModules = 0;
bool favorite = false;
bool courseCompleted = false;

50. Quick Revision Table

ConceptMeaning
StateInformation that can change during a widget's lifetime
StatefulWidgetWidget that works with mutable state
StateObject that stores mutable state and UI logic
createState()Creates the State object
initState()Used for one-time initialization
build()Describes the current UI
setState()Notifies Flutter that UI-relevant state changed
didChangeDependencies()Responds to changes in inherited dependencies
didUpdateWidget()Responds to updated widget configuration
mountedIndicates whether State is currently in the widget tree
dispose()Releases resources when State is permanently removed

51. Key Takeaways

  • State represents information that can change during a widget's lifetime.
  • StatefulWidget is used for dynamic and interactive UI.
  • The StatefulWidget itself is immutable.
  • Mutable state is normally stored in a separate State object.
  • createState() creates the State object.
  • build() describes the current user interface.
  • setState() notifies Flutter about local state changes that may affect the UI.
  • The callback passed to setState() should remain synchronous.
  • initState() is useful for one-time initialization.
  • didChangeDependencies() can respond to inherited dependency changes.
  • didUpdateWidget() can respond to new widget configuration.
  • dispose() should release resources owned by the State object.
  • mounted indicates whether the State object is currently in the tree.
  • Keeping frequently changing state close to the relevant UI can help reduce unnecessary rebuild work.
  • For complex shared application state, an appropriate state-management architecture can be used instead of putting everything into one StatefulWidget.

52. Learning Resources


Conclusion

Understanding State and StatefulWidget is essential for developing interactive Flutter applications. State represents information that can change, while StatefulWidget provides a structure for associating that changing information with a part of the widget tree.

The most important concepts to remember are StatefulWidget, State, createState(), build(), setState(), initState(), didChangeDependencies(), didUpdateWidget(), mounted, and dispose(). Together, these concepts explain how Flutter manages dynamic UI components throughout their lifecycle.

Once you understand how state changes trigger UI updates, you can build practical applications such as counters, forms, dashboards, shopping carts, login screens, profile pages, timers, and interactive mobile applications.

For further Flutter learning, explore JustAcademy Flutter Training and Register for a Course Demo.

whatsapp